﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting;
using MessageCallAspect.Inspector.MessageSink;

namespace MessageCallAspect.Inspector
{

    public static class ReflectionExtensions
    {
        public static T[] GetAllAttributes<T>(this ICustomAttributeProvider member)
          where T : Attribute
        {
            return member.GetCustomAttributes(typeof(T), true) as T[];
        }

        public static bool HasAttribute<T>(this ICustomAttributeProvider member)
            where T : Attribute
        {
            return member.IsDefined(typeof(T), true);
        }

        public static T[] GetAllAttributes<T>(this MethodInfo mi)
          where T : Attribute
        {
            ICustomAttributeProvider cap = (ICustomAttributeProvider)mi.AsPropertyInfo();
            if (cap == null)
                cap = mi;
            return cap.GetCustomAttributes(typeof(T), true) as T[];
        }

        public static bool HasAttribute<T>(this MethodInfo mi)
            where T : Attribute
        {
            ICustomAttributeProvider cap = (ICustomAttributeProvider)mi.AsPropertyInfo();
            if (cap == null)
                cap = mi;
            return cap.IsDefined(typeof(T), true);
        }

        public static MethodInfo GetInterfaceMethod<TInterface>(this MethodInfo mi)
        {
            return mi.GetInterfaceMethod(typeof(TInterface));
        }
        
        public static MethodInfo GetInterfaceMethod<TInterface>(this MethodInfo mi, bool includeInherited)
        {
            MethodInfo methodInfo = mi.GetInterfaceMethod(typeof(TInterface));
           
            if (methodInfo == null && includeInherited)
            {
                foreach (Type interfaceType in typeof(TInterface).GetInterfaces())
                {
                    methodInfo = mi.GetInterfaceMethod(interfaceType);
                    if (methodInfo != null)
                        break;

                }
            }
            return methodInfo;
        }

        private static MethodInfo GetInterfaceMethod(this MethodInfo mi, Type interfaceType)
        {
            InterfaceMapping interfaceMapping = mi.ReflectedType.GetInterfaceMap(interfaceType);
            for (int i = 0; i < interfaceMapping.TargetMethods.Length; i++)
            {
                if (interfaceMapping.TargetMethods[i] == mi)
                {
                    return interfaceMapping.InterfaceMethods[i];
                }
            }
            return null;
        }

        public static IEnumerable<MethodInfo> GetInterfaceConcreteMethods<TInterface>(this Type type)
        {
            return type.GetInterfaceConcreteMethods(typeof(TInterface));
        }

        public static IEnumerable<MethodInfo> GetInterfaceConcreteMethods<TInterface>(this Type type, bool includeInherited)
        {

            List<MethodInfo> methodsList = type.GetInterfaceConcreteMethods(typeof(TInterface)).ToList();
            if (includeInherited)
            {
                foreach (Type interfaceType in typeof(TInterface).GetInterfaces())
                {
                    methodsList.AddRange(type.GetInterfaceConcreteMethods(interfaceType).Except(methodsList, new MethodComparer()));
                }
            }
            return methodsList;
        }

        private static IEnumerable<MethodInfo> GetInterfaceConcreteMethods(this Type type, Type interfaceType)
        {
            InterfaceMapping interfaceMapping = type.GetInterfaceMap(interfaceType);
            foreach (MethodInfo mi in interfaceMapping.TargetMethods)
            {
                yield return mi;
            }
        }

        public static PropertyInfo AsPropertyInfo(this MethodInfo method)
        {
            PropertyInfo propertyInfo = null;
            TryGetPropertyInfo(method, out propertyInfo);
            return propertyInfo;
        }

        public static bool TryGetPropertyInfo(this MethodInfo method, out PropertyInfo propertyInfo)
        {

            bool takesArg = method.GetParameters().Length == 1;
            bool hasReturn = method.ReturnType != typeof(void);
            if (takesArg != hasReturn)
            {
                if (takesArg)
                {
                    propertyInfo = method.DeclaringType.GetProperties()
                        .Where(prop => prop.GetSetMethod() == method).FirstOrDefault();
                }
                else
                {
                    propertyInfo = method.DeclaringType.GetProperties()
                        .Where(prop => prop.GetGetMethod() == method).FirstOrDefault();
                }
            }
            else
            {
                propertyInfo = null;
            }

            return propertyInfo != null;
        }

        #region MethodComparer

        private class MethodComparer : IEqualityComparer<MethodInfo>
        {
            #region IEqualityComparer<MethodInfo> Members

            public bool Equals(MethodInfo x, MethodInfo y)
            {
                if (x.MethodHandle == y.MethodHandle)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            public int GetHashCode(MethodInfo obj)
            {
                return obj.ToString().ToLower().GetHashCode();
            }

            #endregion
        }

        #endregion
    }

    public static class MessagingExtensions
    {
        public static void ValidateParameters(this IMessage msg)
        {
            IMethodCallMessage mcm = (IMethodCallMessage)msg;

            MethodInfo mi = (MethodInfo)mcm.MethodBase;

            ParameterInfo[] parameters = mi.GetParameters();
            int narg = 0;

            foreach (ParameterInfo pi in parameters)
            {
                object pval = mcm.GetArg(narg++);
                Type pt = pi.ParameterType;
                if (pt.IsByRef) pt = pt.GetElementType();

                if (pval != null && !pt.IsInstanceOfType(pval))
                    throw new RemotingException("Cannot cast argument " + pi.Position + " of type '" + pval.GetType().AssemblyQualifiedName +
                            "' to type '" + pt.AssemblyQualifiedName + "'");
            }
        }

        public static ReturnMessage Wrap(this Exception ex, IMessage msg)
        {
            if ((ex is RemotingException || ex is TargetInvocationException)
                  && ex.InnerException != null)
                ex = ex.InnerException;
            return new ReturnMessage(ex, (IMethodCallMessage)msg);
        }
    }

    public static class LogicExtensions
    {
        public static Func<T, bool> AndAlso<T>(this Func<T, bool> predicate1, Func<T, bool> predicate2)
        {
            return arg => predicate1(arg) && predicate2(arg);
        }
        public static Func<T, bool> OrElse<T>(this Func<T, bool> predicate1, Func<T, bool> predicate2)
        {
            return arg => predicate1(arg) || predicate2(arg);
        }
    }
}
